/*
 * midi.c
 *
 *  Created on: 28-Jun-2009
 *      Author: nrqm
 */

#include "midi.h"

#include "../arduino/WProgram.h"
#include "../arduino/HardwareSerial.h"

#define IS_COMMAND_MSK 0x80	// if this bit is set, then the byte is a command
#define COMMAND_MSK 0xF0	// masks out the command nibble of the command byte
#define CHANNEL_MSK 0x0F	// masks out the channel nibble of the command byte

#define NOTE_OFF_MSK 0x80
#define NOTE_ON_MSK 0x90
#define AFTERTOUCH_MSK 0xA0
#define CONTROLLER_MSK 0xB0
#define PATCH_CHNG_MSK 0xC0
#define CHAN_PRESS_MSK 0xD0
#define PITCH_BEND_MSK 0xE0

extern void print(const char* str);

Midi::Midi() :
	last_command(NOTE_OFF_MSK)
{
	uint8_t i;
	uint8_t j;
	for (i = 0; i < num_commands; i++)
	{
		for (j = 0; j < max_listeners; j++)
		{
			listeners[i][j] = NULL;
		}
	}
	Serial.begin(31250);
}

Midi* Midi::GetInstance()
{
	static Midi instance = Midi();
	return &instance;
}

void Midi::RegisterListener(MidiListener* listener)
{
	uint8_t i;

	// num_commands is the number of unique MIDI commands that the protocol defines; defined in midi.h
	if (listener->GetCommandType() >= 0 && listener->GetCommandType() < num_commands)
	{
		// Find a free space in the listener's command chain.
		for (i = 0; i < max_listeners; i++)
		{
			if (this->listeners[listener->GetCommandType()][i] == NULL)
			{
				break;
			}
		}
		// Make sure that the space is really free (and not just the last space), and set it to the listener object.
		if (this->listeners[listener->GetCommandType()][i] == NULL)
		{
			this->listeners[listener->GetCommandType()][i] = listener;
		}
	}
}

uint8_t Midi::BufferContainsImplicitCommand()
{
	uint8_t byte1 = 0;
	uint8_t byte2 = 0;
	uint8_t result = 0;
	if (buffer.Size() != 2)
	{
		return 0;
	}
	byte1 = buffer.Remove();
	byte2 = buffer.Remove();
	if ((byte1 & IS_COMMAND_MSK) != 0 || (byte2 & IS_COMMAND_MSK) != 0)
	{
		// Then one of the bytes was a command, and we don't have a valid argument.
		result = 0;
	}
	else
	{
		// Then both the bytes are data bytes, sent without a command byte (i.e. the last command,
		// if it was NOTE_ON or PITCH_BEND, is implied).
		result = 1;
	}
	buffer.Add(byte1);
	buffer.Add(byte2);
	return result;
}

void Midi::NotifyListeners(uint8_t byte1, uint8_t byte2)
{
	midi_param_t param;
	uint8_t i = 0;

	switch (last_command)
	{
	case NOTE_ON_MSK:
		param.note_on.note = byte1;
		param.note_on.velocity = byte2;
		for (i = 0; i < max_listeners; i++)
		{
			if (listeners[NOTE_ON][i] != NULL)
			{
				listeners[NOTE_ON][i]->ProcessCommand(&param);
			}
		}
		break;
	case PITCH_BEND_MSK:
		param.pitch_bend.pitch = byte1;		// put byte1 into the 16 bit int so that it can be shifted
		param.pitch_bend.pitch = (param.pitch_bend.pitch << 7) | byte2;		// arguments are 7 bits!
		for (i = 0; i < max_listeners; i++)
		{
			if (listeners[PITCH_BEND][i] != NULL)
			{
				listeners[PITCH_BEND][i]->ProcessCommand(&param);
			}
		}
		break;
	default:
		break;
	}
}

uint8_t Midi::CheckMidi()
{
	uint8_t byte1 = 0;
	uint8_t byte2 = 0;
	uint8_t command_is_implicit = 0;

	if (Serial.available() == 0)
	{
		return 0;
	}

	buffer.Add(Serial.read());

	command_is_implicit = BufferContainsImplicitCommand();

	if (!command_is_implicit && (buffer.Size() != 3 || (buffer.Peek() & IS_COMMAND_MSK) == 0))
	{
		// If the buffer doesn't contain a complete implicit command, and the head of the ring isn't an
		// explicit command byte (or it is an explicit command byte and the ring isn't full yet) then do nothing.
		return 0;
	}

	// At this point the buffer probably contains a complete command message.  If the command byte is explicitly
	// included in the message, then it has to be removed from the buffer before the argument bytes can
	// be accessed.  If the command is implicit, then the argument bytes are at the head of the ring.
	if (!command_is_implicit)
	{
		last_command = buffer.Peek() & COMMAND_MSK;		// extract the command nibble without losing the byte
		last_channel = buffer.Remove() & CHANNEL_MSK;	// extract the channel nibble and remove the byte
	}
	byte1 = buffer.Remove();						// remove the next two bytes, which will be arguments
	byte2 = buffer.Remove();						// buffer should now be empty

	// make sure that the argument bytes aren't commands (could happen if a command with 0 or 1 parameter bytes
	// is received)
	if ((byte1 & IS_COMMAND_MSK) != 0 || (byte2 & IS_COMMAND_MSK) != 0)
	{
		buffer.Add(byte1);
		buffer.Add(byte2);
		return 0;
	}

	NotifyListeners(byte1, byte2);

	return 1;
}

MidiListener::MidiListener(MIDI_COMMAND cmd) :
	command(cmd)
{
	Midi::GetInstance()->RegisterListener(this);
}

MIDI_COMMAND MidiListener::GetCommandType()
{
	return command;
}

void MidiListener::ProcessCommand(midi_param_t* param)
{
	switch (command)
	{
	case NOTE_ON:
		((MidiNoteOnListener*)this)->DoNoteOn(param->note_on.note, param->note_on.velocity);
		break;
	case PITCH_BEND:
		((MidiPitchBendListener*)this)->DoPitchBend(param->pitch_bend.pitch);
		break;
	default:
		break;
	}
}

MidiNoteOnListener::MidiNoteOnListener() :
	MidiListener(NOTE_ON)
{}

MidiPitchBendListener::MidiPitchBendListener() :
	MidiListener(PITCH_BEND)
{}


